/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License");  you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * http//www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 *
 * The Original Code is ART Ontology API.
 *
 * The Initial Developer of the Original Code is University of Roma Tor Vergata.
 * Portions created by University of Roma Tor Vergata are Copyright (C) 2007.
 * All Rights Reserved.
 *
 * ART Ontology API was developed by the Artificial Intelligence Research Group
 * (art.uniroma2.it) at the University of Roma Tor Vergata
 * Current information about the ART Ontology API can be obtained at 
 * http//art.uniroma2.it/owlart
 *
 */

/*
 * Contributor(s): Armando Stellato stellato@info.uniroma2.it
 */
package it.uniroma2.art.owlart.utilities;

import it.uniroma2.art.owlart.exceptions.ModelAccessException;
import it.uniroma2.art.owlart.exceptions.ModelUpdateException;
import it.uniroma2.art.owlart.model.ARTNode;
import it.uniroma2.art.owlart.model.ARTResource;
import it.uniroma2.art.owlart.model.ARTStatement;
import it.uniroma2.art.owlart.model.ARTURIResource;
import it.uniroma2.art.owlart.model.NodeFilters;
import it.uniroma2.art.owlart.models.DirectReasoning;
import it.uniroma2.art.owlart.models.OWLModel;
import it.uniroma2.art.owlart.models.RDFModel;
import it.uniroma2.art.owlart.models.RDFSModel;
import it.uniroma2.art.owlart.navigation.ARTNamespaceIterator;
import it.uniroma2.art.owlart.navigation.ARTResourceIterator;
import it.uniroma2.art.owlart.navigation.ARTStatementIterator;
import it.uniroma2.art.owlart.vocabulary.RDFTypesEnum;
import it.uniroma2.art.owlart.vocabulary.RDFResourceRolesEnum;

import java.util.HashMap;
import java.util.Map;

public class ModelUtilities {

	/**
	 * this method tells if namespace <code>ns</code> is present among the list of namespaces in model
	 * <code>om</code>
	 * 
	 * @param rep
	 * @param ns
	 * @return
	 * @throws ModelAccessException
	 */
	public static boolean isAvailableNamespace(RDFModel om, String ns) throws ModelAccessException {
		ARTNamespaceIterator nss = om.listNamespaces();
		boolean avail = false;
		while (avail == false && nss.streamOpen())
			avail = nss.next().equals(ns);
		return avail;
	}

	/**
	 * given namespace <code>namespace</code>, this tries to automatically suggest a prefix for it
	 * 
	 * @param namespace
	 * @return
	 */
	public static String guessPrefix(String namespace) {
		int lowerCutIndex;
		String tempString;

		if (namespace.endsWith("/") || namespace.endsWith("#"))
			tempString = namespace.substring(0, namespace.length() - 1);
		else
			tempString = namespace;

		if (tempString.matches(".*\\.(\\w{2}|\\w{3})"))
			tempString = tempString.substring(0, tempString.lastIndexOf("."));

		int pointLowerCutIndex = tempString.lastIndexOf(".");
		int slashLowerCutIndex = tempString.lastIndexOf("/");
		if (pointLowerCutIndex > slashLowerCutIndex)
			lowerCutIndex = pointLowerCutIndex;
		else
			lowerCutIndex = slashLowerCutIndex;

		return tempString.substring(lowerCutIndex + 1);
	}

	/**
	 * given the baseuri of an ontology, returns the baseuri
	 * 
	 * @param baseuri
	 * @return
	 */
	public static String createDefaultNamespaceFromBaseURI(String baseuri) {
		if (baseuri.endsWith("/") || baseuri.endsWith("#"))
			return baseuri;
		else
			return baseuri + "#";
	}

	// STARRED:TODO si potrebbe usare una (multi?, non ricordo come si chiama)map per eliminare il beware
	// qui sotto spiegato nei docs. in pratica avrei delle mappe con chiavi gli elementi toBeDeleted e con
	// object delle liste di properties (invece che l'ultima trovata, che sovrascrive le precedenti)
	/**
	 * this method helps in deleting individuals and their associated info. the DeletePropagationPropertyTree
	 * is used to distinguish which linked nodes in the graph should be deleted together with the main
	 * individual <br/>
	 * Beware that each deleted resources generates a propagation starting from its first root propagation
	 * property, so if a deleted individual has properties matching more than one tree, only the first match
	 * is used.
	 * 
	 * @param resource
	 * @param repository
	 * @throws ModelUpdateException
	 */
	public static void deepDeleteIndividual(ARTResource resource, RDFModel repository,
			DeletePropagationPropertyTree delPropTree) throws ModelUpdateException {
		repository.deleteTriple(NodeFilters.ANY, NodeFilters.ANY, resource); // 1) removes all the incoming
		// edges

		Map<ARTResource, ARTResource> deletePropagation = new HashMap<ARTResource, ARTResource>();
		ARTStatementIterator stit;

		try {
			stit = repository.listStatements(resource, NodeFilters.ANY, NodeFilters.ANY, false); // 2) mark
			// all
			// objects
			// pointed
			// through outcoming edges as
			// "to be deleted"
			while (stit.streamOpen()) {
				ARTStatement st = stit.next();
				ARTResource predicate = st.getPredicate();
				ARTNode object = st.getObject();
				if (delPropTree.hasChild(predicate) && object.isResource()) // the second check should be
					// useless, since the chosen
					// propagation property should be
					// only object properties or
					// simple properties linking to
					// resources (not literals)
					deletePropagation.put(object.asResource(), predicate);
			}
			stit.close();
		} catch (ModelAccessException e) {
			throw new ModelUpdateException(e);
		}
		repository.deleteTriple(resource, NodeFilters.ANY, NodeFilters.ANY); // 3)
		for (ARTResource object : deletePropagation.keySet()) { // outcoming edges from resource to objects
			// have already been deleted by last
			// operation; if there are further incoming
			// edges to these objects, then they should
			// not be deleted (that is, the object has its
			// own existence in the ontology which is
			// independent from the deleted instance)
			try {
				stit = repository.listStatements(NodeFilters.ANY, NodeFilters.ANY, object, false);
				// TODO changed this to accomodate for possible use in RDFModel, but with the inner check for
				// repository type it is really unefficient. Modify it accordingly
				if ((repository instanceof RDFSModel) && !((RDFSModel) repository).isClass(object)
						&& !repository.isProperty(object) && !stit.streamOpen())
					// so far: checks that new object is not a class or property, and that it has no other
					// incoming connections
					deepDeleteIndividual(object, repository, delPropTree
							.getChildFromProperty(deletePropagation.get(object)));
				stit.close();
			} catch (ModelAccessException e) {
				throw new ModelUpdateException(e);
			}
		}
	}

	/**
	 * returns the number of resources which are <em>direct</em> instances of <code>cls</code>
	 * 
	 * @param model
	 * @param cls
	 * @return
	 * @throws ModelAccessException
	 */
	public static int getNumberOfClassInstances(DirectReasoning model, ARTResource cls, boolean direct,
			ARTResource... graphs) throws ModelAccessException {
		ARTResourceIterator it;
		if (direct)
			it = model.listDirectInstances(cls, graphs);
		else
			it = model.listInstances(cls, true, graphs);
		int instCounter = 0;
		while (it.streamOpen()) {
			instCounter++;
			it.getNext();
		}
		it.close();
		return instCounter;
	}

	/**
	 * this method checks that there is at least a triple referencing the given named resource
	 * <code>res</code> seeAlso: {@link RDFModel#retrieveURIResource(String, ARTResource...)}
	 * 
	 * @param model
	 * @param res
	 * @return
	 * @throws ModelAccessException
	 */
	public static boolean checkExistingResource(RDFModel model, ARTResource res) throws ModelAccessException {
		if (model.hasTriple(res, NodeFilters.ANY, NodeFilters.ANY, true))
			return true;
		if (model.hasTriple(NodeFilters.ANY, NodeFilters.ANY, res, true))
			return true;
		if (res.isURIResource())
			if (model.hasTriple(NodeFilters.ANY, res.asURIResource(), NodeFilters.ANY, true))
				return true;

		return false;
	}

	/**
	 * this gets the ontType of the resource taken from the {@link RDFTypesEnum} enumerated class.<br/>
	 * This method distinguishes between
	 * <ul>
	 * <li>cls</li>
	 * <li>objectProperty, datatypeProperty, annotationProperty and property</li>
	 * <li>ontology</li>
	 * <li>individual</li>
	 * </ul>
	 * <br/>
	 * 
	 * @param resource
	 * @param ontModel
	 * @throws ModelAccessException
	 */
	public static RDFResourceRolesEnum getResourceRole(ARTResource resource, RDFSModel ontModel)
			throws ModelAccessException {
		if ((ontModel instanceof OWLModel) && ((OWLModel)ontModel).isDataRange(resource))
			return RDFResourceRolesEnum.dataRange;

		if (ontModel.isClass(resource))
			return RDFResourceRolesEnum.cls;
		if (resource.isURIResource()) {
			ARTURIResource uriValue = resource.asURIResource();

			if (ontModel.isProperty(uriValue)) {
				if (ontModel instanceof OWLModel) {
					if (((OWLModel) ontModel).isObjectProperty(uriValue))
						return RDFResourceRolesEnum.objectProperty;
					if (((OWLModel) ontModel).isDatatypeProperty(uriValue))
						return RDFResourceRolesEnum.datatypeProperty;
					if (((OWLModel) ontModel).isAnnotationProperty(uriValue))
						return RDFResourceRolesEnum.annotationProperty;
					if (((OWLModel) ontModel).isOntologyProperty(uriValue))
						return RDFResourceRolesEnum.ontologyProperty;
				}
				return RDFResourceRolesEnum.property;
			}

			if ((ontModel instanceof OWLModel) && ((OWLModel) ontModel).isOntology(uriValue))
				return RDFResourceRolesEnum.ontology;
		}

		return RDFResourceRolesEnum.individual;
	}

}
